/*
 * Copyright (C) 2010-2011 Comcast Cable.  All Rights Reserved.
 */
package com.hercules.android.receiver.handlers.commands;

import com.hercules.android.receiver.AndroidReceiver;
import com.hercules.android.receiver.model.commands.XRECommand;
import com.hercules.android.receiver.model.objects.XREObject;
import com.hercules.android.receiver.model.objects.XREView;
import com.hercules.android.receiver.painting.XREObjectPainter;
import com.hercules.android.receiver.painting.XREObjectPainterException;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * This class stores a map of XREObjectPainters, to be used for painting the affected views by the new or modified view
 * or resource associated with a view. To allow for a generic implementation, this class will use an extra pre-painting
 * handler of XREAuxiliaryCommandHandler type, which should be called by the concrete handlers before performing the
 * actual painting. Post painting functionality can be plugged in using the post command handler from the
 * XRECommandHandler.
 * <p>
 * A view finder utility can also be set in this instance, to be used by the subclasses. The utility will take the
 * receiver and the command as arguments. This utility will return an array of XREViews, representing the affected
 * views. The subclasses will paint each affected view.
 * <p>
 * <b>Thread Safety:</b> This class is not thread safe since it has mutable fields. But if they are not changed after
 * first configured, then this class is used in a thread safe manner.
 *
 * @author liuliquan, 7realm
 * @author 7realm
 * @version 1.0
 */
public abstract class XREObjectCommandHandler extends XRECommandHandler {
    /**
     * The auxiliary handler called before performing the actual painting.
     * <p>
     * Initial value is null. Can be any value. If it is null then no pre processing will be performed. It has getter
     * and setter.
     */
    private AuxiliaryXRECommandHandler prePaintingHandler;

    /**
     * The map from XREObject class to corresponding painters.
     * <p>
     * Initial value is null. Not null. Not empty. The key must be not null and not empty. The value must be not null.
     * It has getter and setter.
     */
    private Map<String, XREObjectPainter> objectPainters;

    /**
     * The utility used to find affected view.
     * <p>
     * Defaults to a DefaultXREViewFinderUtility instance in constructor. Not null. It has getter and setter.
     */
    private XREViewFinderUtility affectedViewFinder;

    /**
     * Creates a new instance of {@link XREObjectCommandHandler}.
     */
    protected XREObjectCommandHandler() {
        affectedViewFinder = new DefaultXREViewFinderUtility();
    }

    /**
     * Gets the map from XREObject class to corresponding painters.
     *
     * @return the map from XREObject class to corresponding painters
     */
    public Map<String, XREObjectPainter> getObjectPainters() {
        // return shallow copy
        return objectPainters == null ? null : new HashMap<String, XREObjectPainter>(objectPainters);
    }

    /**
     * Sets the map from XREObject class to corresponding painters.
     *
     * @param objectPainters the map from XREObject class to corresponding painters
     * @throws IllegalArgumentException if given map is null or empty; or if it contains null/empty key; of if it
     * contains null value
     */
    public void setObjectPainters(Map<String, XREObjectPainter> objectPainters) {
        // check argument
        CommandHandlersUtil.checkMap(objectPainters, "objectPainters");

        // set to shallow copy
        this.objectPainters = new HashMap<String, XREObjectPainter>(objectPainters);
    }

    /**
     * Gets the auxiliary handler called before performing the actual painting.
     *
     * @return the auxiliary handler called before performing the actual painting
     */
    public AuxiliaryXRECommandHandler getPrePaintingHandler() {
        return prePaintingHandler;
    }

    /**
     * Sets the auxiliary handler called before performing the actual painting.
     *
     * @param prePaintingHandler the auxiliary handler called before performing the actual painting
     */
    public void setPrePaintingHandler(AuxiliaryXRECommandHandler prePaintingHandler) {
        this.prePaintingHandler = prePaintingHandler;
    }

    /**
     * Gets the utility used to find affected view.
     *
     * @return the utility used to find affected view
     */
    public XREViewFinderUtility getAffectedViewFinder() {
        return affectedViewFinder;
    }

    /**
     * Sets the utility used to find affected view.
     *
     * @param affectedViewFinder the utility used to find affected view
     * @throws IllegalArgumentException if given finder is null
     */
    public void setAffectedViewFinder(XREViewFinderUtility affectedViewFinder) {
        // check argument
        CommandHandlersUtil.checkNotNull(affectedViewFinder, "affectedViewFinder");

        this.affectedViewFinder = affectedViewFinder;
    }

    /**
     * Paint the list of affected views.
     *
     * @param affectedViews the list of affected views
     * @param command the XRE command which affects the views
     * @param receiver the receiver for the XRE application
     * @throws IllegalArgumentException if given command or receiver is null; or if given affectedViews list is null or
     * contains null element
     * @throws XREObjectCommandHandlerException if any error occurs while painting the views
     */
    protected void paintAffectedViews(List<XREView> affectedViews, XRECommand command, AndroidReceiver receiver)
        throws XREObjectCommandHandlerException {
        // check arguments
        CommandHandlersUtil.checkNotNull(command, "command");
        CommandHandlersUtil.checkNotNull(receiver, "receiver");
        CommandHandlersUtil.checkNotNull(affectedViews, "affectedViews");
        for (XREView xreView : affectedViews) {
            if (xreView == null) {
                throw new IllegalArgumentException("Argument 'affectedViews' contains null element.");
            }
        }

        // check object painters
        if (objectPainters == null) {
            throw new XREObjectCommandHandlerException("Value of property 'this.objectPainters' is null.");
        }

        for (XREView affectedView : affectedViews) {
            // get object painter name
            XREObject painterObject = affectedView.getResource() == null ? affectedView : affectedView.getResource();

            // get object painter
            XREObjectPainter painter = objectPainters.get(painterObject.getClass().getSimpleName());
            if (painter == null) {
                if (getLogger() != null) {
                    getLogger().warning("No Painter for '" + painterObject.getClass().getSimpleName() + "' object.");
                }
            } else {
                try {
                    painter.paintView(receiver, affectedView, command);
                } catch (XREObjectPainterException e) {
                    throw new XREObjectCommandHandlerException("Error occurred while painting affected view.", e);
                }
            }
        }
    }

    /**
     * Calls pre paint handler.
     * <p>
     * AndroidAssembly1 this method was moved from helper to this class
     *
     * @param receiver the android receiver
     * @param command the XRE command
     * @throws XREObjectCommandHandlerException if error occurred while executing handler
     */
    protected void callPrePaintHandler(AndroidReceiver receiver, XRECommand command)
        throws XREObjectCommandHandlerException {
        if (prePaintingHandler != null) {
            try {
                prePaintingHandler.processCommandMessage(command, receiver);
            } catch (XRECommandHandlerException e) {
                throw new XREObjectCommandHandlerException(
                    "Error occurred while processing command with pre painting handler.", e);
            }
        }
    }
}